﻿using System;
using System.Collections.Generic;
using System.Threading;
using Moq;
using NUnit.Framework;

namespace PoolKit.Tests
{
    [TestFixture]
    public class ResourceEvictionManagerTests
    {
        private static ResourceEvictionManager<string> CreateResourceEvictionManagerWithMocks()
        {
            return new ResourceEvictionManager<string>(
                new Mock<ICommandProcessor>().Object, new Mock<IResourceCollectionUsageTracking<string>>().Object,
                TimeSpan.FromMinutes(10),
                11);
        }

        [Test]
        public void MakeSureYouCannotDoJackWithDisposedEvictionManager()
        {
            ResourceEvictionManager<string> resourceEvictionManager = CreateResourceEvictionManagerWithMocks();

            resourceEvictionManager.Dispose();

            DisposablePatternTester.MakeSureDisposedObjectIsUnusable(resourceEvictionManager);
        }

        [Test]
        public void MakeSureYouCannotStartEvictionManagerTwiceInARow()
        {
            ResourceEvictionManager<string> resourceEvictionManager = CreateResourceEvictionManagerWithMocks();

            resourceEvictionManager.Start();

            Assert.Throws<InvalidOperationException>(resourceEvictionManager.Start);
        }

        [Test]
        public void MakeSureYouCannotStopEvictionManagerThatIsNotRunning()
        {
            ResourceEvictionManager<string> resourceEvictionManager = CreateResourceEvictionManagerWithMocks();

            resourceEvictionManager.Start();
            resourceEvictionManager.Stop();

            Assert.Throws<InvalidOperationException>(resourceEvictionManager.Stop);
        }

        [Test]
        public void MakeSureEvictionKicksInAfterProperAmountOfTimePassesAlsoCheckResourcesDisposing()
        {
            // Note: I realize it's a fragile functional test

            using (var commandProcessor = new CommandProcessorThreadPoolBased())
            {
                var resources = new List<IDisposable>();

                const int initialResourceCount = 15;
                const int minimumCountOfResourcesForEvictionToKickIn = 11;
                int resourceDisposeCallCount = 0;

                for (int i = 0; i < initialResourceCount; i++)
                {
                    var mock = new Mock<IDisposable>();
                    mock.Setup(x => x.Dispose()).Callback(() => resourceDisposeCallCount++);
                    resources.Add(mock.Object);
                }

                var resourceCollection = new ResourceCollection<IDisposable>(resources);
                
                using (var resourceEvictionManager =
                    new ResourceEvictionManager<IDisposable>(
                    commandProcessor,
                    new ResourceCollectionUsageTracking<IDisposable>(resourceCollection, resources, new StopwatchFactory()), 
                    TimeSpan.FromMilliseconds(100),
                    minimumCountOfResourcesForEvictionToKickIn))
                {
                    Assert.AreEqual(initialResourceCount, resourceCollection.Count);

                    resourceEvictionManager.Start();

                    // giving it time to run the eviction
                    Thread.Sleep(500);

                    Assert.AreEqual(minimumCountOfResourcesForEvictionToKickIn, resourceCollection.Count);
                    Assert.AreEqual(initialResourceCount-minimumCountOfResourcesForEvictionToKickIn, resourceDisposeCallCount);
                }
            }
        }

        [Test]
        public void MakeSureHeavyLoadDoesNotSendEvictionManagerOffTrack()
        {
            // Note: I realize it's a fragile functional test

            using (var commandProcessor = new CommandProcessorThreadPoolBased())
            {
                var resources = new List<string>();

                const int initialResourceCount = 15;
                const int minimumCountOfResourcesForEvictionToKickIn = 11;

                for (int i = 0; i < initialResourceCount; i++)
                {
                    resources.Add(Guid.NewGuid().ToString("N"));
                }

                var resourceCollection = new ResourceCollection<string>(resources);

                var resourceCollectionTracking =
                    new ResourceCollectionUsageTracking<string>(resourceCollection, resources, new StopwatchFactory());

                var thread1 = new Thread(delegate()
                    {
                        for (int i = 0; i < 2000; i++)
                        {
                            resourceCollectionTracking.Add(Guid.NewGuid().ToString("N"));
                        }
                    });

                var thread3 = new Thread(delegate()
                    {
                        for (int i = 0; i < 2000; i++)
                        {
                            resourceCollectionTracking.Add(Guid.NewGuid().ToString("N"));
                        }
                    });

                var thread2 = new Thread(delegate()
                    {
                        var resourcesTaken = new List<string>();

                        for (int i = 0; i < 1500; i++)
                        {
                            string resource;
                            if (resourceCollectionTracking.TryTake(out resource))
                            {
                                resourcesTaken.Add(resource);
                            }
                        }

                        Thread.Sleep(150);

                        foreach (string s in resourcesTaken)
                        {
                            resourceCollectionTracking.AcceptBack(s);
                        }
                    });

                using (var resourceEvictionManager =
                    new ResourceEvictionManager<string>(
                    commandProcessor,
                    resourceCollectionTracking,
                    TimeSpan.FromMilliseconds(150),
                    minimumCountOfResourcesForEvictionToKickIn))
                {
                    resourceEvictionManager.Start();

                    thread1.Start();
                    thread2.Start();

                    thread1.Join();
                    thread2.Join();

                    Thread.Sleep(200);

                    thread3.Start();
                    thread3.Join();

                    // giving it time to run the eviction
                    Thread.Sleep(400);

                    Assert.AreEqual(minimumCountOfResourcesForEvictionToKickIn, resourceCollectionTracking.Count);
                }
            }
        }
    }
}